package com.lnu_devteam.zongce.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.InvalidClaimException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.lnu_devteam.zongce.constant.JWTConstant;
import com.lnu_devteam.zongce.constant.UserConstant;
import com.lnu_devteam.zongce.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import static com.lnu_devteam.zongce.common.ErrorCode.*;

/**
 * @author xbaozi
 * @version 1.0
 * @classname JWTUtils
 * @date 2022-12-30  00:23
 * @description JWT工具类
 */
@Slf4j
@Component
public class JWTUtils {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * @description 生成token
     * @author xbaozi
     * @date 2023/4/7 14:03
     * @param payload   负载，即存放的内容
     * @return String
     **/
    public String generateToken(String payload) {
        try {
            // 私钥和加密算法
            Algorithm algorithm = Algorithm.HMAC256(JWTConstant.SECRET);
            // 设置头部信息
            Map<String, Object> header = new HashMap<>(2);
            header.put("Type", "Jwt");
            header.put("alg", "HS256");

            String token = JWT.create()
                    .withHeader(header)
                    // 设置payload负载
                    .withClaim(JWTConstant.HEAD_NAME, payload)
                    // 设置生成时间
                    .withIssuedAt(new Date())
                    // 设置签名算法
                    .sign(algorithm);
            stringRedisTemplate.opsForValue().set(UserConstant.LOGIN_KEY + payload, token, JWTConstant.expire, JWTConstant.TIME_UNIT);
            return token;
        } catch (Exception e) {
            log.info("generate token occur error, error is: ", e);
            throw new BusinessException(SYSTEM_ERROR);
        }
    }

    /**
     * @description 校验token
     * @author xbaozi
     * @date 2023/4/7 14:19
     **/
    @NotNull
    public DecodedJWT verifyToken(String token) {
        DecodedJWT verify = null;
        try {
            verify = JWT.require(
                    Algorithm.HMAC256(JWTConstant.SECRET)
            ).build().verify(token);
            if (verify != null) {
                String tokenInRedis = stringRedisTemplate.opsForValue().get(
                        UserConstant.LOGIN_KEY + verify.getClaim(JWTConstant.HEAD_NAME).asString()
                );
                if (!token.equals(tokenInRedis)) {
                    verify = null;
                }
            }
        } catch (SignatureVerificationException e) {
            // 签名不一致
            throw new BusinessException(SIGNATURE_VERIFICATION);
        }  catch (AlgorithmMismatchException e) {
            // 签名算法不匹配
            throw new BusinessException(ALGORITHM_MISMATCH);
        } catch (InvalidClaimException e) {
            // payload不可用
            throw new BusinessException(INVALID_CLAIM);
        } catch (Exception e) {
            // 非格式正确token，校验错误
            throw new BusinessException(VERIFY_ERROR);
        }
        if (verify == null) {
            log.error("verify token fail");
            throw new BusinessException(VERIFY_ERROR);
        }
        return verify;
    }

    /**
     * @description 通过token获取负载
     * @author xbaozi
     * @date 2023/4/7 14:18
     **/
    public String getSubject(String token) {
        // 验证token
        DecodedJWT decodedJWT = verifyToken(token);
        // 解析成功，直接返回
        return decodedJWT.getClaim(JWTConstant.HEAD_NAME).asString();
    }

    /**
     * @description 续签token
     * @author xbaozi
     * @date 2023/4/7 14:33
     **/
    public void refreshToken(String token) {
        String payload = getSubject(token);
        stringRedisTemplate.opsForValue().set(UserConstant.LOGIN_KEY + payload, token, JWTConstant.expire, JWTConstant.TIME_UNIT);
    }

    /**
     * @description 移除token
     * @author xbaozi
     * @date 2023/4/7 15:04
     **/
    public void removeToken(String token) {
        String payload = this.getSubject(token);
        stringRedisTemplate.delete(UserConstant.LOGIN_KEY + payload);
    }

}
